/* Copyright Contributors to the Open Shading Language project.
 * SPDX-License-Identifier: BSD-3-Clause
 * https://github.com/imageworks/OpenShadingLanguage
 */


/** Lexical scanner for Open Shading Language
 **/

/************************************************************
 * Definitions section
 ************************************************************/


/* Option 'noyywrap' indicates that when EOF is hit, yyin does not
 * automatically reset to another file.
 */
%option noyywrap
%option nounput noinput

/* never-interactive fixes a Windows compatibility problem where the
 * lexer emits isatty calls that don't exist.
 */
%option never-interactive

 /* Option 'prefix' creates a C++ lexer with the given prefix, so that
  * we can link with other flex-generated lexers in the same application
  * without name conflicts.
  */
%option prefix="osl"


 /* Define regular expression macros
  ************************************************/

 /* white space, not counting newline */
WHITE           [ \t\v\f\r]+
 /* alpha character */
ALPHA           [A-Za-z]
 /* numerals */
DIGIT           [0-9]
 /* Integer literal */
INTEGER         {DIGIT}+
HEXINTEGER      0[xX][0-9a-fA-F]+
 /* floating point literal (E, FLT1, FLT2, FLT3 are just helpers)
  * NB: we don't allow leading +/- due to ambiguity between
  * whether "a-0.5" is really "a -0.5" or "a - 0.5".  Resolve this
  * in the grammar.
  */
E               [eE][-+]?{DIGIT}+
FLT1            {DIGIT}+\.{DIGIT}*{E}?
FLT2            {DIGIT}*\.{DIGIT}+{E}?
FLT3            {DIGIT}+{E}
FLT             {FLT1}|{FLT2}|{FLT3}
 /* string literal */
STR     \"(\\.|[^\\"\n])*\"
        /* " This extra quote fixes emacs syntax highlighting on this file */
 /* Identifier: alphanumeric, may contain digits after the first character */
IDENT           ({ALPHA}|[_])({ALPHA}|{DIGIT}|[_])*
 /* C preprocessor (cpp) directives */
CPP             ^[ \t]*#.*\n
CPLUSCOMMENT    \/\/.*\n



 /* Note for lex newbies: the following '%{ .. %}' section contains literal
  * C code that will be inserted at the top of code that flex generates.
  */
%{
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <string>

#include <OpenImageIO/thread.h>
#include <OpenImageIO/strutil.h>
#include "oslcomp_pvt.h"

using namespace OSL;
using namespace OSL::pvt;

#include "oslgram.hpp"   /* Generated by bison/yacc */

#define yylval osllval
#define yylloc osllloc

#ifdef _WIN32
#define YY_NO_UNISTD_H

#ifdef _MSC_VER
#define strtoll _strtoi64
#include <io.h>
#define isatty _isatty
#endif
#endif

// flex itself will generate fatal warnings about signed vs unsigned.
// Bypass that nonsense.
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 6
#pragma GCC diagnostic ignored "-Wsign-compare"
#endif

// flex uses the 'register' keyword, warned because it's deprecated in C++17.
#if defined(__clang__)
#pragma GCC diagnostic ignored "-Wdeprecated-register"
#endif

void preprocess (const char *yytext);

// Macro that sets the yylloc line variables to the current parse line.
#define SETLINE yylloc.first_line = yylloc.last_line = oslcompiler->lineno()

%}


%%

 /************************************************
  * Lexical matching rules
  ************************************************/

 /* preprocessor symbols */
{CPP}                   {  preprocess (yytext); SETLINE; }

 /* Comments */
{CPLUSCOMMENT}          {  oslcompiler->incr_lineno(); /* skip it */
                           SETLINE;
                        }

 /* keywords */
"break"                 {  SETLINE;  return (yylval.i=BREAK); }
"closure"               {  SETLINE;  return (yylval.i=CLOSURE); }
"color"                 {  SETLINE;  return (yylval.i=COLORTYPE); }
"continue"              {  SETLINE;  return (yylval.i=CONTINUE); }
"do"                    {  SETLINE;  return (yylval.i=DO); }
"else"                  {  SETLINE;  return (yylval.i=ELSE); }
"float"                 {  SETLINE;  return (yylval.i=FLOATTYPE); }
"for"                   {  SETLINE;  return (yylval.i=FOR); }
"if"                    {  SETLINE;  return (yylval.i=IF_TOKEN); }
"illuminance"           {  SETLINE;  return (yylval.i=ILLUMINANCE); }
"illuminate"            {  SETLINE;  return (yylval.i=ILLUMINATE); }
"int"                   {  SETLINE;  return (yylval.i=INTTYPE); }
"matrix"                {  SETLINE;  return (yylval.i=MATRIXTYPE); }
"normal"                {  SETLINE;  return (yylval.i=NORMALTYPE); }
"output"                {  SETLINE;  return (yylval.i=OUTPUT); }
"point"                 {  SETLINE;  return (yylval.i=POINTTYPE); }
"public"                {  SETLINE;  return (yylval.i=PUBLIC); }
"return"                {  SETLINE;  return (yylval.i=RETURN); }
"string"                {  SETLINE;  return (yylval.i=STRINGTYPE); }
"struct"                {  SETLINE;  return (yylval.i=STRUCT); }
"vector"                {  SETLINE;  return (yylval.i=VECTORTYPE); }
"void"                  {  SETLINE;  return (yylval.i=VOIDTYPE); }
"while"                 {  SETLINE;  return (yylval.i=WHILE); }
"or"                    {  SETLINE;  return (yylval.i=OR_OP); }
"and"                   {  SETLINE;  return (yylval.i=AND_OP); }
"not"                   {  SETLINE;  return (yylval.i=NOT_OP); }

 /* reserved words */
"bool"|"case"|"char"|"class"|"const"|"default"|"double" |    \
"enum"|"extern"|"false"|"friend"|"inline"|"long"|"private" | \
"protected"|"short"|"signed"|"sizeof"|"static"|"struct" |    \
"switch"|"template"|"this"|"true"|"typedef"|"uniform" |      \
"union"|"unsigned"|"varying"|"virtual" {
                            oslcompiler->errorf(oslcompiler->filename(),
                                                oslcompiler->lineno(),
                                                "'%s' is a reserved word",
                                                yytext);
                            SETLINE;
                            return (yylval.i=RESERVED);
                        }


 /* Identifiers */
{IDENT}                 {
                            yylval.s = ustring(yytext).c_str();
                            SETLINE;
                            return IDENTIFIER;
                        }

 /* Literal values */
{INTEGER}               {
                            long long llval = strtoll (yytext, (char**)NULL, 10);
                            // we do not detect overflow when the value is INT_MAX+1,
                            // because negation happens later and -(INT_MAX+1) == INT_MIN
                            if (llval > ((long long)INT_MAX)+1) {
                                oslcompiler->errorf(oslcompiler->filename(),
                                                    oslcompiler->lineno(),
                                                    "integer overflow, value must be between %d and %d.",
                                                    INT_MIN, INT_MAX);
                            }
                            yylval.i = (int)llval;
                            SETLINE;
                            return INT_LITERAL;
                        }
{HEXINTEGER}            {
                            long long llval = strtoll (yytext, (char**)NULL, 16);
                            // we do not detect overflow when the value is INT_MAX+1,
                            // because negation happens later and -(INT_MAX+1) == INT_MIN
                            if (llval > ((long long)UINT_MAX)+1) {
                                oslcompiler->errorf(oslcompiler->filename(),
                                                    oslcompiler->lineno(),
                                                    "integer overflow, value must be between %d and %d.",
                                                    INT_MIN, INT_MAX);
                            }
                            yylval.i = (int)llval;
                            SETLINE;
                            return INT_LITERAL;
                        }



{FLT}                   {
                            yylval.f = OIIO::Strutil::from_string<float>(yytext);
                            SETLINE;
                            return FLOAT_LITERAL;
                        }

{STR}                   {
                            // grab the material between the quotes
                            string_view s(yytext + 1, yyleng - 2);
                            std::string unescaped;
                            if (s.find('\\') != string_view::npos) {
                                // Only make a new string if we must unescape
                                unescaped = OIIO::Strutil::unescape_chars(s);
                                s = string_view(unescaped);
                            }
                            yylval.s = ustring(s).c_str();
                            SETLINE;
                            // std::cerr << "osllex string '" << yylval.s << "'\n";
                            return STRING_LITERAL;
                        }

 /* The one-char operators (like "+") will return correctly with the
  * catch-all rule, but we need to define the two-character operators
  * so they are not lexed as '+' and '=' separately, for example.
  */
"+="                    {  SETLINE;  return (yylval.i=ADD_ASSIGN); }
"-="                    {  SETLINE;  return (yylval.i=SUB_ASSIGN); }
"*="                    {  SETLINE;  return (yylval.i=MUL_ASSIGN); }
"/="                    {  SETLINE;  return (yylval.i=DIV_ASSIGN); }
"&="                    {  SETLINE;  return (yylval.i=BIT_AND_ASSIGN); }
"|="                    {  SETLINE;  return (yylval.i=BIT_OR_ASSIGN); }
"^="                    {  SETLINE;  return (yylval.i=XOR_ASSIGN); }
"<<="                   {  SETLINE;  return (yylval.i=SHL_ASSIGN); }
">>="                   {  SETLINE;  return (yylval.i=SHR_ASSIGN); }
"<<"                    {  SETLINE;  return (yylval.i=SHL_OP); }
">>"                    {  SETLINE;  return (yylval.i=SHR_OP); }
"&&"                    {  SETLINE;  return (yylval.i=AND_OP); }
"||"                    {  SETLINE;  return (yylval.i=OR_OP); }
"<="                    {  SETLINE;  return (yylval.i=LE_OP); }
">="                    {  SETLINE;  return (yylval.i=GE_OP); }
"=="                    {  SETLINE;  return (yylval.i=EQ_OP); }
"!="                    {  SETLINE;  return (yylval.i=NE_OP); }
"++"                    {  SETLINE;  return (yylval.i=INCREMENT); }
"--"                    {  SETLINE;  return (yylval.i=DECREMENT); }

 /* Beginning of metadata */
"[["                    {  SETLINE;  return (yylval.i=METADATA_BEGIN); }

 /* End of line */
"\\\n"                  |
[\n]                    {  oslcompiler->incr_lineno();
                           SETLINE;
                        }

 /* Ignore whitespace */
{WHITE}                 {  }

 /* catch-all rule for any other single characters */
!                       {  SETLINE;  return (yylval.i = NOT_OP); }
.                       {  SETLINE;  return (yylval.i = *yytext); }

%%


void
preprocess (const char *yytext)
{
#if 0
    printf ("preprocess: <%s>\n", yytext);
#endif
    const char *p = yytext;
    while (*p == ' ' || *p == '\t')
        p++;
    if (*p != '#') {
        oslcompiler->errorf(oslcompiler->filename(), oslcompiler->lineno(),
                            "Possible bug in shader preprocess");
        SETLINE;
        return;
    }
    p++;
    while (*p == ' ' || *p == '\t')
        p++;
    if (! strncmp (p, "pragma", 6)) {
        // pragma
        OIIO::string_view line (p+6);
        string_view pragmatype = OIIO::Strutil::parse_word (line);
        if (OIIO::Strutil::iequals (pragmatype, "osl")) {
            string_view pragmaname = OIIO::Strutil::parse_word (line);
            if (pragmaname == "nowarn") {
                oslcompiler->pragma_nowarn ();
            } else {
                oslcompiler->warningf(oslcompiler->filename(), oslcompiler->lineno(),
                                      "Unknown pragma '%s'", pragmaname);
            }
        }
        // N.B. Pragmas that don't start with "osl" are ignored
        oslcompiler->incr_lineno();  // the pragma ends with an EOLN
    } else {  /* probably the line number and filename */
        if (! strncmp (p, "line", 4))
            p += 4;
        int line = atoi (p);
        if (line > 0) {
            const char *f = strchr (yytext, '\"'); // " undo syntax highlight
            if (f) {
                ++f;  // increment to past the quote
                int len = 0;  // count of chars within quotes
                while (f[len] && f[len] != '\"') // " undo syntax highlight
                    ++len;
                std::string filename (f, len);
                // Make it relative to the working directory, so error output
                // isn't hugely cluttered (and also to make Boost::wave's
                // error output match the standard cpp.
                if (filename.find (oslcompiler->cwd()) == 0) {
                    filename.erase (0, oslcompiler->cwd().size());
                    if (filename.size() &&
                        (filename[0] == '/' || filename[0] == '\\'))
                        filename.erase (0, 1);
                }
                ustring ufilename(filename);
                oslcompiler->filename (ufilename);
                // Spooky workaround for Boost Wave bug: force_include
                // is broken and doesn't give us the right lines/files, so
                // instead we forcefully insert a '#include "stdosl.h"' into
                // the stream ourselves, but this in turn makes the rest
                // of the main file all have line counts one line off!
                // So we fix it here, ugh.
                if (ufilename == oslcompiler->main_filename())
                    --line;
            }
            oslcompiler->lineno (line);
        } else {
            oslcompiler->errorf(oslcompiler->filename(), oslcompiler->lineno(),
                                "Unrecognized preprocessor command: #%s", p);
        }
    }
    SETLINE;
}



OSL_NAMESPACE_ENTER
namespace pvt {   // OSL::pvt


bool
OSLCompilerImpl::osl_parse_buffer (const std::string &preprocessed_buffer)
{
    // N.B. This should only be called if oslcompiler_mutex is held.
    OSL_ASSERT (oslcompiler == this);

#ifndef OIIO_STRUTIL_HAS_STOF
    // Force classic "C" locale for correct '.' decimal parsing.
    // N.B. This is not safe in a multi-threaded program where another
    // application thread is expecting the native locale to work properly.
    // This is not necessary for versions of OIIO that have Strutil::stof,
    // and we can remove it entirely when OIIO 1.9 is the minimum.
    std::locale oldlocale = std::locale::global (std::locale::classic());
#endif

    osl_switch_to_buffer (osl_scan_string (preprocessed_buffer.c_str()));
    oslparse ();
    bool parseerr = error_encountered();
    osl_delete_buffer (YY_CURRENT_BUFFER);
#ifndef OIIO_STRUTIL_HAS_STOF
    std::locale::global (oldlocale);  // Restore the original locale.
#endif
    return parseerr;
}

}; // namespace pvt
OSL_NAMESPACE_EXIT
